#include "objectmodel.h"
#include <QMetaObject>
#include <QMetaProperty>
#include <QDebug>

ObjectModel::ObjectModel(QObject *parent)
    : QAbstractListModel{parent}
{
}

ObjectModel::ObjectModel(const QStringList &titles, QObject *parent)
    : ObjectModel{parent}
{
    headers = titles;
}

ObjectModel::~ObjectModel()
{
}

int ObjectModel::rowCount(const QModelIndex &) const
{
    return items.size();
}

int ObjectModel::columnCount(const QModelIndex &) const
{
    auto obj = items.first();
    if(obj) {
        auto meta = obj->metaObject();
        return meta->propertyCount() - meta->propertyOffset();
    } else {
        return 0;
    }
}

Qt::ItemFlags ObjectModel::flags(const QModelIndex &index) const
{
    int row = index.row();

    if(!index.isValid() || row > items.count()) {
        return Qt::NoItemFlags;
    }

    auto item = items.at(row);
    auto meta = item->metaObject();

    auto offset = index.column() + meta->propertyOffset();
    auto prop = meta->property(offset);
    if(!prop.isReadable()) {
        return Qt::NoItemFlags;
    }

    auto f =  QAbstractItemModel::flags(index);
    if(prop.isWritable()) {
        f |= Qt::ItemIsEditable;
    }

    return f;
}

QVariant ObjectModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::EditRole)) {
        return QVariant();
    }

    auto item = items.at(index.row());
    auto meta = item->metaObject();

    auto propIdx = index.column() + meta->propertyOffset();
    auto prop = meta->property(propIdx);
    // qDebug() << "prop:" << prop.name();
    return item->property(prop.name());
}

bool ObjectModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid() || (role != Qt::EditRole)) {
        return false;
    }

    auto item = items.at(index.row());
    auto meta = item->metaObject();

    auto propIdx = index.column() + meta->propertyOffset();
    auto prop = meta->property(propIdx);
    return item->setProperty(prop.name(), value);
}

QVariant ObjectModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role != Qt::DisplayRole) {
        return QVariant();
    }

    if(orientation == Qt::Vertical) {
        return section + 1;
    }

    if(section < headers.size()) {
        return headers.at(section);
    }

    auto obj = items.first();
    if(obj) {
        auto meta = obj->metaObject();
        auto offset = section + meta->propertyOffset();
        auto prop = meta->property(offset);
        return prop.name();
    } else {
        return QVariant();
    }
}

bool ObjectModel::removeRows(int row, int count, const QModelIndex &parent)
{
    if(row < 0 || row >= items.count()) {
        return false;
    }

    beginRemoveRows(parent, row, row + count - 1);
    for(int i = 0; i < count && row < items.count(); i++) {
        auto item = items.takeAt(row);
        item->deleteLater();
    }
    endRemoveRows();

    return true;
}

bool ObjectModel::insert(int row, QObject *item)
{
    Q_ASSERT(item != nullptr);
    if(row < 0 || row > items.count()) {
        return false;
    }

    beginInsertRows(QModelIndex(), row, row);
    items.insert(row, item);
    endInsertRows();

    auto thisMeta = this->metaObject();
    auto slotIdx = thisMeta->indexOfSlot("onPropChanged()");
    auto slot = thisMeta->method(slotIdx);

    auto meta = item->metaObject();
    for(int i = meta->propertyOffset(); i < meta->propertyCount(); i++) {
        auto prop = meta->property(i);
        if(prop.hasNotifySignal()) {
            auto signal = prop.notifySignal();
            connect(item, signal, this, slot);
        }
    }

    return true;
}

void ObjectModel::append(QObject *object)
{
    Q_ASSERT(object != nullptr);
    insert(items.size(), object);
}

void ObjectModel::clear()
{
    removeRows(0, items.count());
}

QObject *ObjectModel::item(int row) const
{
    if(row < 0 || row >= items.count()) {
        return nullptr;
    }

    return items.at(row);
}

bool ObjectModel::removeItem(QObject *object)
{
    int row = items.indexOf(object);

    if(row < 0) {
        return false;
    }

    emit itemRemoved(row);
    return removeRows(row, 1);
}

void ObjectModel::notifyItemChanged(const QObject *item)
{
    if(item == nullptr) {
        return;
    }

    int row = -1;

    for(int i = 0; i < items.size(); i ++) {
        if(items[i] == item) {
            row = i;
            break;
        }
    }

    if(row < 0) {
        return;
    }

    auto meta = item->metaObject();
    auto columCount = meta->propertyCount() - meta->propertyOffset();
    auto idxFrom = index(row, 0);
    auto idxTo = index(row, columCount - 1);

    emit dataChanged(idxFrom, idxTo);
}

void ObjectModel::onPropChanged()
{
    auto item = sender();
    int row = -1;

    for(int i = 0; i < items.size(); i ++) {
        if(items[i] == item) {
            row = i;
            break;
        }
    }

    if(row < 0) {
        return;
    }

    auto meta = item->metaObject();
    int column = senderSignalIndex() - meta->methodOffset();
    QModelIndex idx = index(row, column);

    emit dataChanged(idx, idx);
}
